﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

#warning check whether this class is up-to-date (compare to wintools)
#warning remove unused stuff

namespace Deskopeia.Helper
{
    [Serializable]
    public class DesktopItem
    {
        public int Index { get; protected internal set; }
        public string Text { get; protected internal set; }
        public Point Location { get; protected internal set; }
        public Win32APIImports.RECT Bounds { get; protected internal set; }
    }

    [Serializable]
    public class DesktopInfo
    {
        public IntPtr HWnd { get; protected internal set; }
        public Win32APIImports.RECT Bounds { get; protected internal set; }
        public Size GridSize { get; protected internal set; }
    }

    class DesktopHelper
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
                uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress,
             uint dwSize, uint dwFreeType);

        [DllImport("kernel32.dll")]
        public static extern bool CloseHandle(IntPtr handle);

        [DllImport("kernel32.dll")]
        public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
             IntPtr lpBuffer, int nSize, ref uint vNumberOfBytesRead);

        [DllImport("kernel32.dll")]
        public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
             IntPtr lpBuffer, int nSize, ref uint vNumberOfBytesRead);

        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(uint dwDesiredAccess,
                bool bInheritHandle, uint dwProcessId);

        private const uint LVM_FIRST = 0x1000;
        private const uint LVM_GETITEMCOUNT = LVM_FIRST + 4;
        private const uint LVM_GETITEMRECT = LVM_FIRST + 14;
        private const uint LVM_SETITEMPOSITION = LVM_FIRST + 15;
        private const uint LVM_GETITEMPOSITION = LVM_FIRST + 16;
        private const uint LVM_GETITEMSPACING = LVM_FIRST + 51;
        private const uint LVM_GETITEMW = LVM_FIRST + 75;

        private const uint PROCESS_VM_OPERATION = 0x0008;
        private const uint PROCESS_VM_READ = 0x0010;
        private const uint PROCESS_VM_WRITE = 0x0020;
        private const uint MEM_COMMIT = 0x1000;
        private const uint MEM_RELEASE = 0x8000;
        private const uint MEM_RESERVE = 0x2000;
        private const uint PAGE_READWRITE = 4;
        private const int LVIF_TEXT = 0x0001;

        struct LVITEM
        {
            public int mask;
            public int iItem;
            public int iSubItem;
            public int state;
            public int stateMask;
            public IntPtr pszText; // string
            public int cchTextMax;
            public int iImage;
            public IntPtr lParam;
            public int iIndent;
            public int iGroupId;
            public int cColumns;
            public IntPtr puColumns;
        }

        int HiWord(int value)
        {
            return (value >> 16) & 0xffff;
        }
        int LoWord(int value)
        {
            return value & 0xffff;
        }


        /// <summary>
        /// /////////////////////////////////////////////////////////////////////////////////////////////////
        /// </summary>

        public List<DesktopItem> DesktopItems { get; protected set; }
        public DesktopInfo DesktopInfo { get; set; }

        public DesktopHelper()
            : this(true)
        {
        }

        public DesktopHelper(bool refreshData)
        {
            DesktopItems = new List<DesktopItem>();
            DesktopInfo = new DesktopInfo();

            if (refreshData)
                RefreshData();
        }

        public void RefreshData()
        {
            DesktopItems.Clear();

            IntPtr desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SysListView32);
            DesktopInfo.HWnd = desktop;

            int iconCount = (int)Win32APIImports.SendMessage(desktop, LVM_GETITEMCOUNT, IntPtr.Zero, IntPtr.Zero);

            int spacing = (int)Win32APIImports.SendMessage(desktop, LVM_GETITEMSPACING, IntPtr.Zero, IntPtr.Zero);
            DesktopInfo.GridSize = new Size(LoWord(spacing), HiWord(spacing));

            DesktopInfo.Bounds = Win32APIImports.GetWindowRectangle(desktop);

            uint numberOfBytesWritten = 0;
            uint numberOfBytesRead = 0;

            uint processId;
            Win32APIImports.GetWindowThreadProcessId(desktop, out processId);

            IntPtr hProcessDesktop = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, processId);
            IntPtr desktopVMPtr = VirtualAllocEx(hProcessDesktop, IntPtr.Zero, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

            try
            {
                for (int i = 0; i < iconCount; i++)
                {
                    //fill buffer with icon text
                    byte[] buffer = new byte[256];

                    LVITEM[] lvi = new LVITEM[1];
                    lvi[0].mask = LVIF_TEXT;
                    lvi[0].iItem = i;
                    lvi[0].iSubItem = 0;
                    lvi[0].cchTextMax = buffer.Length;
                    lvi[0].pszText = (IntPtr)((int)desktopVMPtr + Marshal.SizeOf(typeof(LVITEM)));

                    WriteProcessMemory(hProcessDesktop, desktopVMPtr, Marshal.UnsafeAddrOfPinnedArrayElement(lvi, 0), Marshal.SizeOf(typeof(LVITEM)), ref numberOfBytesWritten);
                    Win32APIImports.SendMessage(desktop, LVM_GETITEMW, (IntPtr)i, desktopVMPtr);
                    ReadProcessMemory(hProcessDesktop, (IntPtr)((int)desktopVMPtr + Marshal.SizeOf(typeof(LVITEM))), Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0), buffer.Length, ref numberOfBytesRead);

                    string itemText = Encoding.Unicode.GetString(buffer, 0, (int)numberOfBytesRead);
                    itemText = itemText.Substring(0, itemText.IndexOf('\0'));


                    //fill itemBounds with icon bounds
                    Win32APIImports.RECT[] itemBounds = new Win32APIImports.RECT[1];
                    Win32APIImports.SendMessage(desktop, LVM_GETITEMRECT, (IntPtr)i, desktopVMPtr);
                    ReadProcessMemory(hProcessDesktop, desktopVMPtr, Marshal.UnsafeAddrOfPinnedArrayElement(itemBounds, 0), Marshal.SizeOf(typeof(Win32APIImports.RECT)), ref numberOfBytesRead);


                    //fill itemPosition with icon position
                    Point[] itemPosition = new Point[1];
                    Win32APIImports.SendMessage(desktop, LVM_GETITEMPOSITION, (IntPtr)i, desktopVMPtr);
                    ReadProcessMemory(hProcessDesktop, desktopVMPtr, Marshal.UnsafeAddrOfPinnedArrayElement(itemPosition, 0), Marshal.SizeOf(typeof(Point)), ref numberOfBytesRead);


                    DesktopItems.Add(new DesktopItem()
                    {
                        Index = i,
                        Bounds = itemBounds[0],
                        Location = itemPosition[0],
                        Text = itemText
                    });
                }
            }
            finally
            {
                VirtualFreeEx(hProcessDesktop, desktopVMPtr, 0, MEM_RELEASE);
                CloseHandle(hProcessDesktop);
            }
        }

        public int GetDesktopItemCount()
        {
            return (int)Win32APIImports.SendMessage(DesktopInfo.HWnd, LVM_GETITEMCOUNT, IntPtr.Zero, IntPtr.Zero);
        }

        public void SetItemPosition(DesktopItem desktopItem, int x, int y)
        {
            SetItemPosition(desktopItem, new Point(x, y));
        }

        public void SetItemPosition(DesktopItem desktopItem, Point pos)
        {
            //because of the discrepancy between position of the boundary of the item and the position of the icon only...
            //the problem is: when using ListView_SetItemPosition, the position afterwards isn't the expected one:
            //the position of the icon has been set, but not the position of the whole item
            //this difference can be calculated by subtract the position (ListView_GetItemPosition)
            //from the left and right values of the bounds (ListView_GetItemRect)
            int xOffset = desktopItem.Location.X - desktopItem.Bounds.Left;
            int yOffset = desktopItem.Location.Y - desktopItem.Bounds.Top;

            int x = pos.X + xOffset;
            int y = pos.Y + yOffset;

            if ((int)Win32APIImports.SendMessage(DesktopInfo.HWnd, LVM_SETITEMPOSITION,
               (IntPtr)desktopItem.Index, Win32APIImports.MakeLParam(x, y)) == 1)
            {
                desktopItem.Location = new Point(x, y);
                desktopItem.Bounds = new Win32APIImports.RECT(pos.X, pos.Y, pos.X + desktopItem.Bounds.Width, pos.Y + desktopItem.Bounds.Height);
            }
        }

        public bool GetDesktopAlignToGrid()
        {
            int LVS_EX_SNAPTOGRID = 0x00080000;
            uint LVM_GETEXTENDEDLISTVIEWSTYLE = 0x1037;
            IntPtr desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SysListView32);
            return ((Win32APIImports.SendMessage(desktop, LVM_GETEXTENDEDLISTVIEWSTYLE, IntPtr.Zero, IntPtr.Zero).ToInt32() & LVS_EX_SNAPTOGRID) == LVS_EX_SNAPTOGRID);
        }

        public bool GetDesktopAutoArrange()
        {
            int LVS_AUTOARRANGE = 0x0100;
            IntPtr desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SysListView32);
            return ((Win32APIImports.GetWindowLongPtr(desktop, Win32APIImports.WindowLongConstants.GWL_STYLE) & LVS_AUTOARRANGE) == LVS_AUTOARRANGE);
        }

        public void SetDesktopAlignToGrid(bool value)
        {
            bool alignToGridActive = GetDesktopAlignToGrid();

            //if toogle is needed
            if (value != alignToGridActive)
            {
                OperatingSystem os = Environment.OSVersion;

                IntPtr desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SHELLDLL_DefViewParent);

                if (os.Platform == PlatformID.Win32NT && os.Version.Major == 6 && os.Version.Minor == 1) //Seven
                    desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SHELLDLL_DefView);

                Win32APIImports.SendMessage(desktop, Win32APIImports.WindowMessages.WM_COMMAND, (IntPtr)0x7074, IntPtr.Zero);
            }
        }

        public void SetDesktopAutoArrange(bool value)
        {
            bool autoArrangeActive = GetDesktopAutoArrange();

            //if toogle is needed
            if (value != autoArrangeActive)
            {
                OperatingSystem os = Environment.OSVersion;

                IntPtr desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SHELLDLL_DefViewParent);

                if (os.Platform == PlatformID.Win32NT && os.Version.Major == 6 && os.Version.Minor == 1) //Seven
                    desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SHELLDLL_DefView);

                Win32APIImports.SendMessage(desktop, Win32APIImports.WindowMessages.WM_COMMAND, (IntPtr)0x7071, IntPtr.Zero);
            }
        }

        public void RefreshDesktop()
        {
            OperatingSystem os = Environment.OSVersion;

            IntPtr desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.ProgMan);

            if (os.Platform == PlatformID.Win32NT && os.Version.Major == 6 && os.Version.Minor == 1) //Seven
                desktop = DesktopWindowHelper.GetDesktopWindow(DesktopWindowHelper.DesktopWindow.SHELLDLL_DefView);

            Win32APIImports.SendMessage(desktop, Win32APIImports.WindowMessages.WM_COMMAND, (IntPtr)0x7103, IntPtr.Zero);
        }

        /// <summary>
        /// save the position of each desktop item into a binary file
        /// </summary>
        public bool SaveDesktopItems(string file)
        {
            bool result = false;
            try
            {
                //Serialization
                FileStream fileStream;
                fileStream = new FileStream(file, FileMode.Create);
                BinaryFormatter binFormatter = new BinaryFormatter();
                binFormatter.Serialize(fileStream, DesktopItems);
                fileStream.Close();

                result = true;
            }
            catch (Exception)
            {
                result = false;
            }

            return result;
        }

        /// <summary>
        /// restores the position of each desktop item from a binary file
        /// </summary>
        public bool RestoreDesktopItems(string file)
        {
            bool result = false;

            try
            {
                if (File.Exists(file))
                {
                    // Deserialization
                    BinaryFormatter binFormatter = new BinaryFormatter();
                    FileStream fileStream = new FileStream(file, FileMode.Open);
                    DesktopItems = (List<DesktopItem>)binFormatter.Deserialize(fileStream);
                    fileStream.Close();

                    foreach (DesktopItem item in DesktopItems)
                    {
                        SetItemPosition(item, item.Bounds.Location);
                    }
                    result = true;
                }
            }
            catch (Exception)
            {
                result = false;
            }

            return result;
        }
    }
}
